/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  /**
   * 这里是构建SqlSessionfactory,构建成功会返回一个默认的SqlSessionFactory=DefaultSqlSessionFactory
   * 这个build方法主要就是读取mybatis的配置文件，里面包含了很多的配置信息，
   * 比如数据源的配置，事物的配置；我这里主要是读取resource下面的MyBatis.xml的配置文件
   * 在这个配置文件主要解析了configuration标签，这个就是mybatis的总的上下文的一个配置文件
   * 也就是说它具有加载数据源、执行sql语句，拿到执行结果，这里涉及了好几个感念，其中configuration解析的标签
   * 在mybatis中也有一个对应的对象Configuration，其中解析的结果就会封装成要一个Configuration对象
   * Configuration：管理配置文件mysql-config.xml配置文件
   * SqlSessionFactory：Session管理工厂接口
   * Session：SqlSession是一个面向用户（程序员)的接口，SqlSession中提供了很多操作数据库的方法
   * Executor：执行器是一个接口（基本执行器，缓存执行器）作用：SqlSession内部通过执行器操作数据库
   * MappedStatement：底层封装的对象，对操作数据库存储封装，包括sql语句、输入输出参数
   * StatementHandler：具体操作数据库相关的handler接口
   * ResultSetHandler:具体操作数据库返回结果的handler接口
   * @param inputStream
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      /**
       * inputStream中是我们的全局上下文配置文件的一个输入流对象
       * environment：是环境信息
       * properties：是引入的配置文件之一
       * 这里会将这几个对象进行封装，其中会将inputStream流创建为一个Documet对象，然后返回一个解析的对象
       * XMLConfigBuilder，所做的事情：
       * 1.将inputstream流创建为一个Document对象，然后封装在对象XPathParser中；
       * 2.new Configuration（）对象得到configuration；
       * 3.如果environment不为空，设置全局的environment为传入的environment；
       * 4.如果properties不为空，则将传入的properties设置到configuration中的variables中。
       */
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      /**
       * 这里有两个操作
       * 1.parser.parse()方法就是开始解析Mybatis.xml配置文件，得到一个Configuration对象，而这个
       * Configuration对象就是上面new出来的configuration对象；
       * 2.调用build方法将上下文configuration对象封装成一个DefaultSqlSessionFactory，也就是我们常常
       * 看见的SqlSessionFactory
       * 所以核心的重点是parser.parse()方法，需要重点分析
       */
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
